var fs=require("fs");

/**
 * Class: DocGen
 *
 * Properties:
 *		classes							typeof Array
 *		classes[].description
 *		classes[].properties
 *		classes[].name
 *		classes[].
 *
 *		function_sections				typeof Array
 *		controller_sections				typeof Array
 *
 *		_now							typeof Object
 *		_now.section					typeof String	- one of: "class", "function", "method", "controller"
 *		_now.object						typeof Object	- current object
 *		_now.subsection					typeof String	- one of "properties", "parameters", "returns"
 */


/**
 * Method: new
 *
 * Parameters:
 *		config typeof Object
 *		config.srcpath typeof String
 *		config.dstpath typeof String
 *		config.ignore typeof String,optional
 */
function DocGen(config,rr)
{
	this.srcpath=config.srcpath;
	this.dstpath=config.dstpath;
	var i=this.ignore={};
	if (config.ignore) {
		config.ignore.split(/,/).forEach(function(a) { i[a]=1; });
	}

	if (!this.srcpath.match(/\/$/)) this.srcpath+="/";
	if (!this.dstpath.match(/\/$/)) this.dstpath+="/";
	this.rr=rr;
}

/**
 * Method: init
 */
DocGen.prototype.init=function()
{
	this.classes=[];
	this.function_sections=[];
	this.controller_sections=[];
	this._now={};
}

/**
 * Method: list_files
 *		Recursively list files
 *
 * Parameters:
 *		basepath typeof String			-
 *		path typeof String, optional	-
 *		arr typeof Array, optional		-
 *
 * Returns:
 *		typeof Array of String			- Array of file names relative to basepath
 */
DocGen.prototype.list_files=function(basepath,path,arr)
{
//	system.stdout.writeLine("DocGen.list_files("+basepath+","+path+",[...])");
	var t=this;
	if (!path) path="";
	if (!arr) arr=[];
	var dir=new fs.Directory(basepath+path);
	if (!dir.exists()) {
		return [];
	}
//	system.stdout.writeLine("DocGen.list_files - fs.Directory() = "+dir);
	
	dir.listFiles().forEach(function(f) {
		if (f.match(/^\./)) return;
		if (f.match(/^combined-\d+/)) return;
		arr.push(path+f);
	});
	dir.listDirectories().forEach(function(f) {
		if (f.match(/^\./)) return;
		if (t.ignore[f]) return;
		var fh=new fs.File(basepath+path+f+"/");
		//throw t.rr.Dumper(fn.stat());
//		if (fh.stat().mode && 0xA000) return;
		t.list_files(basepath,path+f+"/",arr);
	});
	return arr;
}

/**
 * Method: parse
 *		Parses all files
 */
DocGen.prototype.parse=function()
{
	this.init();
	var files=this.list_files(this.srcpath).filter(function(f) { return f.match(/\.js$/i); });
	for (var i=0;i<files.length;i++) this.parse_file(files[i]);
}

/**
 * Method: parse_file
 *
 * Parameters:
 *		filename typeof String	
 */
DocGen.prototype.parse_file=function(filename)
{
//	system.stdout.writeLine("DocGen.parse_file("+this.srcpath+filename+")");
	var f=new fs.File(this.srcpath+filename);
	f.open("r");
	try {
		var text=f.read().toString("utf-8");
	} catch (e) {
		throw "DocGen.parse_file("+this.srcpath+filename+") - error loading file\n"+e;
	}
	f.close();
	var lines=text.split(/\n/);

	this._now={section:"",subsection:""};
	var is_cmt=false;
	this.file=filename;
	for (var i=0;i<lines.length;i++) {
		this.lineno=i+1;
		var line=lines[i];
		if (line.match(/^\s*\/\*\*/)) {is_cmt=1;continue;}
		if (line.match(/^\s*\*\//)) {is_cmt=0;this._now.section="";continue;}
		if (is_cmt) this.parse_line(line.replace(/\s+$/,"").replace(/^\s*\*\s*/,""));
	}
}

/**
 * Method: parse_line
 *		Parses line, according to this._now
 */
DocGen.prototype.parse_line=function(line)
{
//	if (this.file=="models/ProductCategoryField.js") throw "line="+line;
	var arr=line.match(/^(Method|Function|Class|Attributes|Controller|Properties|Parameters|Returns)\s*:\s*(\S*)/);
	if (arr) {
		this["parse_line_"+arr[1]](arr[2]);
		return;
	}
	if (this._now.subsection.match(/^(parameters|properties|returns)$/)) {
//		this.error("TEST, value for this._now.subsection - "+this._now.subsection);
		this.parse_line_params(line);
		return;
	} else if (this._now.subsection) {
		this.error("unknown value for this._now.subsection - "+this._now.subsection);
	}
	if (this._now.object) {
		if (this._now.object.description) this._now.object.description+="\n";
		this._now.object.description+=line;
	}
}

/**
 * Method: parse_line_params
 *		Parses line with params to function/method/controller, such as call parameters, return values, or class properties
 */
DocGen.prototype.parse_line_params=function(line)
{
	var data={name:"",optional:0,defaults:"",is_param:false,description:"",datatype:""};
	line=line.replace(/\s+-(.*)/,function(p,p1) { data.description=p1;return "";});
	line=line.replace(/\s*,\s*optional/,function() { data.optional=1; return "";});
	line=line.replace(/\s*,\s*default (.*)/,function(p,p1) { data.defaults=p1; return "";});
	line=line.replace(/\s*typeof (.*)/,function(p,p1) { data.datatype=p1; return "";});
	data.name=line;
	if (data.name.match(/^\w+$/)) data.is_param=true;
	switch (this._now.subsection) {
		case "parameters":
			this._now.object.parameters.push(data);
			break;
		case "returns":
			this._now.object.returns.push(data);
			break;
		case "properties":
			this._now.object.properties.push(data);
			break;
	}
	this._now.object.anylength++;
}

/**
 * Method: parse_line_Class
 */
DocGen.prototype.parse_line_Class=function(name)
{
	if (!name) this.error("Class declaration is missing a class name");
	if (this.find_by_name(this.classes,name)) this.error("Class with this name is already defined");
	this._now.section="class";
	this._now.subsection="";
	this._now.classname=name;
	var o={name:name,description:"",properties:[],methods:[],filename:this.file,lineno:this.lineno,attributes:{},anylength:0};
	this.classes.push(o);
	this._now.object=o;
}

/**
 * Method: parse_line_Attributes
 */
DocGen.prototype.parse_line_Attributes=function(attrs)
{
	if (!attrs) this.error("Attributes declaration is missing attributes list");
	if (!this._now.object) this.error("No object to add attributes");
	var aa=this._now.object.attributes;
	attrs.split(/\s*\,\s*/).forEach(function(a) { aa[a]=1; });
}

/**
 * Method: parse_line_Properties
 */
DocGen.prototype.parse_line_Properties=function(attrs)
{
	if (!this._now.object) this.error("No object to start properties");
	if (this._now.section!="class") this.error("Not a class section, section='"+this._now.section+"'");
	this._now.subsection="properties";
}

/**
 * Method: parse_line_Parameters
 */
DocGen.prototype.parse_line_Parameters=function(attrs)
{
	if (!this._now.object) this.error("No object to start parameters");
	if (!this._now.section.match(/^(method|function|controller)$/)) this.error("Not a method || function || controller - '"+this._now.section+"'");
	this._now.subsection="parameters";
}

/**
 * Method: parse_line_Returns
 */
DocGen.prototype.parse_line_Returns=function(attrs)
{
	if (!this._now.object) this.error("No object to start returns");
	if (!this._now.section.match(/^(method|function|controller)$/)) this.error("Not a method || function || controller - '"+this._now.section+"'");
	this._now.subsection="returns";
}

/**
 * Method: parse_line_Method
 */
DocGen.prototype.parse_line_Method=function(name)
{
	if (!name) this.error("Method declaration is missing a method name");
	if (!this._now.classname) this.error("Method declaration before a class declaration");
	this._now.section="method";
	this._now.subsection="";
	var c=this.find_by_name(this.classes,this._now.classname);
	if (this.find_by_name(c.methods,name)) this.error("Method with this name is already declared for this class");
	var o={name:name,fullname:/*this._now.classname+"."+*/name,description:"",parameters:[],returns:[],filename:this.file,lineno:this.lineno,attributes:{},anylength:0};
	c.methods.push(o);
	this._now.object=o;
}

/**
 * Method: parse_line_Controller
 */
DocGen.prototype.parse_line_Controller=function(name)
{
	if (!name) this.error("Controller declaration is missing a controller name");
	var arr=name.split(/\./);
	if (arr.length!=2) this.error("Controller declaration with incorrect name format");
	this._now.section="controller";
	this._now.subsection="";
	var cs=this.find_by_name(this.controller_sections,arr[0]);
	if (!cs) {cs={name:arr[0],controllers:[]};this.controller_sections.push(cs);}

	if (this.find_by_name(cs.controllers,arr[1])) this.error("Controller with this name is already declared for this section");
	var o={name:arr[1],fullname:arr[0]+"."+arr[1],description:"",parameters:[],returns:[],filename:this.file,lineno:this.lineno,attributes:{},section:arr[0],anylength:0};
	cs.controllers.push(o);
	this._now.object=o;
}

/**
 * Method: parse_line_Function
 */
DocGen.prototype.parse_line_Function=function(name)
{
	if (!name) this.error("Function declaration is missing a function name");
	var arr=name.split(/\./);
	if (arr.length==1) arr.unshift("_global_");
	var is_file=false;
	if (arr[0]=="_local_") {arr[0]=this.file;is_file=true;}
	if (arr.length!=2) this.error("Function declaration with incorrect name format");
	this._now.section="function";
	this._now.subsection="";
	var cs=this.find_by_name(this.function_sections,arr[0]);
	if (!cs) {cs={name:arr[0],functions:[]};this.function_sections.push(cs);}

	if (this.find_by_name(cs.functions,arr[1])) this.error("Function with name "+arr.join(".")+" is already declared for this section");
	var fullname=(arr[0]=="_global_" || is_file)?arr[1]:arr[0]+"."+arr[1];
	var o={name:arr[1],fullname:fullname,description:"",parameters:[],returns:[],filename:this.file,lineno:this.lineno,attributes:{},section:arr[0],anylength:0};
	cs.functions.push(o);
	this._now.object=o;
}
/**
 * Method: find_by_name
 */
DocGen.prototype.find_by_name=function(arr,name)
{
	for (var i=0;i<arr.length;i++) if (arr[i].name==name) return arr[i];
	return 0;
}


/**
 * Method: error
 */
DocGen.prototype.error=function(text)
{
	var more="";
//	var classes="";
//	for (var i=0;i<this.classes.length;i++) classes+="\t"+this.classes[i].name+"\n";
///	more="Classes:\n"+(classes|| '-none-\n');
	throw "File "+this.srcpath+this.file+", line "+this.lineno+":\n"+text+"\n"+more;
}

/**
 * Method: save
 */
DocGen.prototype.save=function(data,filename)
{
	var f=new fs.File(this.dstpath+filename);
	f.open("w");
	f.write(data);
	f.close();
}
/**
 * Method: copy
 */
DocGen.prototype.copy=function(srcfile,filename)
{
	var f=new fs.File(srcfile);
	f.open("r");
	var data=f.read();
	f.close();
	this.save(data,filename);
}


/**
 * Method: build
 */
DocGen.prototype.build=function()
{
	this.copy(system.getcwd()+"/html/shared/docgen/docgen.css","docgen.css");
	this.rr.docgen=this;
	this.rr.rightblock=this.rr.View("system/docgen/_rightblock");
	this.rr.todos=[];

	this.save(this.build_files(),"files.html");
	for (var i=0;i<this.rr.files.length;i++) {
		this.rr.file=this.rr.files[i];
		this.save(this.rr.View("system/docgen/file"),"file_"+this.rr.file.id+".html");
	}

	this.save(this.build_classes(),"classes.html");
	for (var i=0;i<this.rr.classes.length;i++) {
		this.rr.cl=this.rr.classes[i];
		this.save(this.rr.View("system/docgen/class"),"class_"+this.rr.cl.id+".html");
	}

	this.save(this.build_controller_sections(),"controller_sections.html");
	for (var i=0;i<this.rr.controller_sections.length;i++) {
		this.rr.cs=this.rr.controller_sections[i];
		this.save(this.rr.View("system/docgen/controller"),"controller_"+this.rr.cs.id+".html");
	}

	this.save(this.build_function_sections(),"function_sections.html");
	for (var i=0;i<this.rr.function_sections.length;i++) {
		this.rr.fs=this.rr.function_sections[i];
		this.save(this.rr.View("system/docgen/function"),"function_"+this.rr.fs.id+".html");
	}

	this.save(this.build_todos(),"todos.html");

	this.save(this.build_index(),"index.html");
}

/**
 * Method: build_files
 */
DocGen.prototype.build_files=function()
{
	var t=this;
//	var n=1;
	var files={};
	function add_file_entry(type,info)
	{
		if (!files[info.filename]) files[info.filename]={name:info.filename,items:[],id:t.rr.F("Languages","to_code",info.filename)};
		files[info.filename].items.push({type:type,lineno:info.lineno,name:info.fullname || info.name});
	}
	for (var i=0;i<this.classes.length;i++) add_file_entry("class",this.classes[i]);
	for (var i=0;i<this.function_sections.length;i++) {
		var fs=this.function_sections[i];
		for (var j=0;j<fs.functions.length;j++) {
			add_file_entry("function",fs.functions[j]);
		}
	}
	for (var i=0;i<this.controller_sections.length;i++) {
		var fs=this.controller_sections[i];
		for (var j=0;j<fs.controllers.length;j++) {
			add_file_entry("controller",fs.controllers[j]);
		}
	}

	this.rr.files=[];
	for (var k in files) this.rr.files.push(files[k]);
	this.rr.files.sort(function(a,b) { if (a.name<b.name) return -1; if (a.name>b.name) return 1; return 0;});
	return this.rr.View("system/docgen/files");
}

/**
 * Method: build_classes
 */
DocGen.prototype.build_classes=function()
{
	this.rr.classes=this.classes;
	for (var i=0;i<this.rr.classes.length;i++) this.rr.classes[i].id=this.rr.F("Languages","to_code",this.rr.classes[i].name);
	this.rr.classes.sort(function(a,b) { if (a.name<b.name) return -1; if (a.name>b.name) return 1; return 0;});
	return this.rr.View("system/docgen/classes");
}

/**
 * Method: build_controller_sections
 */
DocGen.prototype.build_controller_sections=function()
{
	this.rr.controller_sections=this.controller_sections;
	for (var i=0;i<this.rr.controller_sections.length;i++) this.rr.controller_sections[i].id=this.rr.F("Languages","to_code",this.rr.controller_sections[i].name);
	this.rr.controller_sections.sort(function(a,b) { if (a.name<b.name) return -1; if (a.name>b.name) return 1; return 0;});
	return this.rr.View("system/docgen/controller_sections");
}

/**
 * Method: build_function_sections
 */
DocGen.prototype.build_function_sections=function()
{
	this.rr.function_sections=this.function_sections;
	for (var i=0;i<this.rr.function_sections.length;i++) this.rr.function_sections[i].id=this.rr.F("Languages","to_code",this.rr.function_sections[i].name);
	this.rr.function_sections.sort(function(a,b) { if (a.name<b.name) return -1; if (a.name>b.name) return 1; return 0;});
	return this.rr.View("system/docgen/function_sections");
}

/**
 * Method: build_todos
 */
DocGen.prototype.build_todos=function()
{
	for (var i=0;i<this.classes.length;i++) {
		var cl=this.classes[i];
		if (cl.attributes.TODO) this.rr.todos.push({type:"class",value:cl});
		for (var j=0;j<cl.methods.length;j++) {
			var m=cl.methods[j];
			if (m.attributes.TODO) this.rr.todos.push({type:"method",value:m,parent:cl});
		}
	}
	for (var i=0;i<this.controller_sections.length;i++) {
		var cs=this.controller_sections[i];
		for (var j=0;j<cs.controllers.length;j++) {
			var c=cs.controllers[j];
			if (c.attributes.TODO) this.rr.todos.push({type:"controller",value:c,parent:cs});
		}
	}
	for (var i=0;i<this.function_sections.length;i++) {
		var fs=this.function_sections[i];
		for (var j=0;j<fs.functions.length;j++) {
			var f=fs.functions[j];
			if (f.attributes.TODO) this.rr.todos.push({type:"function",value:f,parent:fs});
		}
	}
	return this.rr.View("system/docgen/todos");
}

/**
 *
 */
DocGen.prototype.build_index=function()
{
	return this.rr.View("system/docgen/index");
}


/**
 *
 */
DocGen.prototype.build_index=function()
{
	return this.rr.View("system/docgen/index");
}

exports.add=[{
	_type: "functions",
	_section: "DocGen",

	/**
	 * Function: DocGen.build
	 *		Generated documentation
	 *
	 * Parameters:
	 *		this.fields.srcpath typeof String
	 *		this.fields.dstpath typeof String
	 */
	build: function() {
		var dg=new DocGen(this.fields,this);
		dg.parse();
		dg.build();
//		throw this.Dumper(dg.classes);
	},

},{
	_type: "controller",
	_config: {
		name:"DocGen",
	},
	short_returns: function() {
		var ret="";
		for (var i=0;i<this.f.returns.length;i++) {
			if (!this.f.returns[i].is_param && this.f.returns[i].name!="") continue;
			if (ret) ret="various"; else ret=this.f.returns[i].datatype;
		}
		return ret || "void";
	},
	short_parameters: function() {
		var ret="";
		for (var i=0;i<this.f.parameters.length;i++) {
			if (!this.f.parameters[i].is_param) continue;
			if (ret) ret+=", ";
			ret+=this.f.parameters[i].name;
		}
		return ret;
	},

	short_attributes: function(config) {
		var a=this[config.field].attributes;
//		throw this[config.field];
		var r="";
		for (var k in a) {
			r+=" <span class='attr attr-"+k+"' alt='"+k+"' title='"+k+"'>"+k.substr(0,1)+"</span>";
		}
		return r;
	},

	long_attributes: function(config) {
		var a=config.pre?this[config.pre][config.field].attributes:this[config.field].attributes;
//		throw this[config.field];
		var r="";
		for (var k in a) {
			r+=" <span class='attr attr-"+k+"'>"+k+"</span>";
		}
		return r;
	}
}];
